//**********************************************************************
//**********************************************************************
//**                                                                  **
//**        (C)Copyright 1985-2014, American Megatrends, Inc.         **
//**                                                                  **
//**                       All Rights Reserved.                       **
//**                                                                  **
//**           5555 Oakbrook Pkwy, Suite 200, Norcross, GA 30093      **
//**                                                                  **
//**                       Phone: (770)-246-8600                      **
//**                                                                  **
//**********************************************************************
//**********************************************************************

/** @file PciInterrupts.c
    PCI Interrupt routing functions

**/


#include <AmiDxeLib.h>
#include <Pci.h>

#include <AcpiRes.h>
#include <Token.h>
#include <Protocol/PciRootBridgeIo.h>
#include <Protocol/DevicePath.h>
#include <Protocol/LegacyBios.h>
#include <Protocol/LegacyBiosPlatform.h>
#include <Protocol/PciIo.h>
#include <Protocol/Legacy8259.h>
#include "csm.h"
#include "AmiCspLib.h"
#include <Protocol/AmiBoardInfo2.h>
#include <Library/AmiSdlLib.h>

VOID                            *gPciIoNotifyReg;
extern EFI_BOOT_SERVICES        *pBS;
BIOS_INFO                       *gCoreBiosInfo;
EFI_LEGACY_PIRQ_TABLE_HEADER    *gMsPrt; // To be obtained after
EFI_LEGACY_IRQ_ROUTING_ENTRY    *gPrt;   // reading GUIDed section.
UINTN   gUsedPciEntries;

extern  BOOLEAN gIsValidPrt=FALSE;

UINTN   CopyLegacyTable(VOID*, UINT16, UINT16, UINT16);

EFI_HANDLE gHandle = NULL;
UINT8   SBGen_GetPIRQIndex (UINT8);

UINT8   irq_priority_map[] = {11, 10, 9, 15, 5, 3, 7, 4, 14};
UINT8   irq_allocated_count[sizeof(irq_priority_map)] = {0};
UINT16  IsaIrqMask;

EFI_ADDON_PCIBUS_TABLE AddonPciBusTable[MAX_ADDITIONAL_P2P_BRIDGES];

UINT8   gAddonPciBusIndx;

EFI_LEGACY_8259_PROTOCOL *i8259;

PROGRAMMED_PCIIRQ_CTX   gIrqPgmCtx = {0};

/**
    This function uses EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.Configuration()
    to obtain the PCI bus number of the bus, generated by the given
    PCI Root Bridge.

    @param hPciRB - PCI Root Bridge handle

    @retval Bus number generated by the Root Bridge

**/

UINT8
GetRBSecondaryBusNum(EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *pRB)
{
    VOID    *Resources;
    UINT8   Bus = 0;
    EFI_STATUS  Status;

    Status = pRB->Configuration(pRB, &Resources);
    ASSERT_EFI_ERROR(Status);

    //
    // Dig through Resources and find the _MIN field value in Bus tag
    //
    while (*(UINT8*)Resources != 0x79) {    // End-of-resources tag
        if (((ASLR_QWORD_ASD*)Resources)->Type == ASLRV_SPC_TYPE_BUS) {
            Bus = (UINT8)((ASLR_QWORD_ASD*)Resources)->_MIN;
            break;
        }
        ((ASLR_QWORD_ASD*)Resources)++;
    }
    return Bus;
}


/**
    This function uses EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.Pci.Read()
    function to obtain the secondary PCI bus number of the P2P
    bridge.
    @param hPciRB - PCI Root Bridge handle
    @param bBus - PCI bus the bridge is located on
    @param bDevFunc - PCI dev/fun of P2Pbridge

    @retval EFI_SUCCESS Bus number generated by the P2P Bridge
    @retval EFI_ACCESS_DENIED P2P bridge is not accessible

**/

EFI_STATUS
GetP2PSecondaryBusNum(
    IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *pRB,
    IN OUT UINT8    *bBus,
    IN UINT8        bDevFun
)
{
    EFI_STATUS  Status;
    UINT32      VidDid;
    UINT64      Address;

    Address = (((*bBus) << 24) + ((bDevFun & 0xF8) << 13) + ((bDevFun & 0x07) << 8)) & 0x00000000ffffffff;

    // See if the device is accessible
    Status = pRB->Pci.Read(pRB, EfiPciWidthUint32,
                Address,
                1,
                &VidDid);
    ASSERT_EFI_ERROR(Status);

    if (VidDid == 0xFFFFFFFF) return EFI_ACCESS_DENIED;

    Address |= PCI_BRIDGE_SECONDARY_BUS_REGISTER_OFFSET;

    // Get the secondary bus number
    Status = pRB->Pci.Read(
                    pRB,
                    EfiPciWidthUint8,
                    Address,
                    1,
                    bBus);
    ASSERT_EFI_ERROR(Status);

    return EFI_SUCCESS;
}


/**
    This routine selects the IRQ from the list of allowed PCI
    interrupts, picks the best interrupt number according to
    interrupt priority table and programs the PCI interrupt
    router.
 
    @param        Int - zero based index in the list of router registers for INTA, INTB,...
    @param        Irq - new interrupt number
    @param        IrqMask - IRQ bit mask, bits are set for the interrupts that are not allowed

    @retval EFI_SUCCESS if interrupt is routed
    @retval EFI_ABORTED if routing register is already programmed

    @note  This routine can not be called externally. It is to be
              called after TranslatePirq returns the Rirq register is
              not programmed.

**/

EFI_STATUS
RoutePciIrq(
    IN  UINT8   Int,
    OUT UINT8   *Irq,
    IN  UINT16  IrqMask
)
{
    UINT16  Mask = ~IrqMask;    // PIC-style mask (1 is for interrupts that are not allowed)
    EFI_LEGACY_INTERRUPT_PROTOCOL   *iInterrupt;
    UINT8   NewIrq;
    EFI_STATUS  Status;
    UINT8 i;
    UINT8   IrqIndex;
    UINT8   IrqFound = FALSE;

    Status = pBS->LocateProtocol (&gEfiLegacyInterruptProtocolGuid, NULL, &iInterrupt);
    if (EFI_ERROR(Status)) return Status;

    //
    // See if routing register is already programmed, return EFI_ABORTED if so.
    //
    Status = iInterrupt->ReadPirq(iInterrupt, Int, &NewIrq);
    ASSERT_EFI_ERROR(Status);
    if (EFI_ERROR(Status)) return Status;

    for (i=0; irq_priority_map[i]; i++) {
        if (NewIrq==irq_priority_map[i]) {
TRACE(((UINTN)-1, "The requested PIRQ[%d] is already programmed to IRQ%x. PCI Routing ABORTED.\n", Int, NewIrq));
            return EFI_ABORTED;
        }
    }

TRACE(((UINTN)-1,"..........IRQ MASK %x....", Mask));
    //
    // Remove ISA interrupts from Mask
    //
    Mask |= IsaIrqMask;

TRACE(((UINTN)-1,"%x....\n", Mask));
    //
    // Find the next available interrupt; irq_priority_map is zero-terminated array
    // used as priority list. Lower index in irq_priority_map indicates higher priority
    // interrupt.
    // Initially irq_priority_index is the index of zero in irq_priority_map; it will
    // be advanced to the beginning in the following for loop.
    //
    for (IrqIndex = 0; IrqIndex < sizeof(irq_priority_map); IrqIndex++) {
TRACE(((UINTN)-1,"IrqIndex %d....Allocated %d....\n", IrqIndex, irq_allocated_count[IrqIndex]));
        if (!((1 << irq_priority_map[IrqIndex]) & Mask)) {
            IrqFound = TRUE;    // Assume that IRQ was found
            for (i = 0; i < sizeof(irq_allocated_count); i++) {
                // Check if the least allocated IRQ
                if (!((1 << irq_priority_map[i]) & Mask)) {
                    if (irq_allocated_count[IrqIndex] > irq_allocated_count[i]) {
                        IrqFound = FALSE;
                        break;
                    }
                }
            }
        }
        if (IrqFound) break;
    }
    if (!IrqFound) return EFI_NOT_FOUND;

    irq_allocated_count[IrqIndex] += 1;
    NewIrq = irq_priority_map[IrqIndex];

    // Adjust irq_priority_index

    Status = iInterrupt->WritePirq(iInterrupt, Int, NewIrq);
    if (EFI_ERROR(Status)) return Status;

    *Irq = NewIrq;
    return EFI_SUCCESS;
}


/**
    This function is similar to TranslatePirq with the additional
    output of IrqMask. See TranslatePirq description.

    @param         This        Indicates the EFI_LEGACY_BIOS_PLATFORM_PROTOCOL instance.
    @param         BusDevFun device and function number for this device.
    @param         Pirq        The PIRQ. PIRQ A = 0, PIRQ B = 1, and so on.
    @param         PirqIrq     IRQ assigned to the indicated PIRQ.
    @param         IrqMask     Mask of IRQs that could be assigned to this register

    @retval        EFI_SUCCESS     The PIRQ was translated.
    @retval        EFI_NOT_FOUND   The device was not in the table.
    @retval        EFI_NOT_READY   The interrupt translation table is not ready.
    @retval        EFI_INVALID_PARAMETER   Wrong input
**/

EFI_STATUS
Intx2Pirq (
  IN  EFI_LEGACY_BIOS_PLATFORM_PROTOCOL  *This,
  IN  UINTN                              PciBus,
  IN  UINTN                              PciDevice,
  IN  UINTN                              PciFunction,
  IN  OUT UINT8                          *Pirq,
  OUT UINT8                              *PciIrq,
  OUT UINT16                             *IrqMask OPTIONAL
  )
{
    UINT8   Irq;
    UINT8   counter;
    EFI_LEGACY_IRQ_ROUTING_ENTRY    *p;
    EFI_LEGACY_INTERRUPT_PROTOCOL   *iInterrupt;
    EFI_STATUS  Status;

    UINT8   PciDev;
    UINT8   rr = 0;     // Router register
    UINT8   rrIndx;     // Router register index
    UINT8   pirq = *Pirq;
    UINT8   i;

    if (!gIsValidPrt) return EFI_NOT_READY;

    if (pirq > 3) {
		TRACE (((UINTN)TRACE_ALWAYS, "Invalid PIRQ value (%d, %d, %d) %d\n", PciBus, PciDevice, PciFunction, pirq));
        return EFI_INVALID_PARAMETER;
    }

    PciDev = (UINT8)(PciDevice << 3);

    for (p = gPrt, counter = 0; counter < gUsedPciEntries; counter++, p++) {
        if ((PciBus == p->Bus) && (PciDev == p->Device)) {
            rr = p->PirqEntry[pirq].Pirq;
            if (IrqMask != NULL) *IrqMask = p->PirqEntry[pirq].IrqMask;
            break;  // rrIndx is found
        }
    }

    if (counter == gUsedPciEntries) {
        if (gAddonPciBusIndx == 0) return EFI_NOT_FOUND; // No additional P2P bridges

        //
        // Not found in &PIR - see if device is generated by the P2P which is not
        // listed in BusNumXlat, e.g. it is behind P2P bridge located on off-board card.
        //
        for (counter = 0; counter < gAddonPciBusIndx; counter++) {
            if (AddonPciBusTable[counter].Bus == PciBus) {
                i = (UINT8)((PciDevice + pirq) % 4);  // INTA/B/C/D for Dev0, INTB/C/D/A for dev1, etc.
                rr = AddonPciBusTable[counter].PirqEntry[i].Pirq;
                if (IrqMask != NULL) *IrqMask = AddonPciBusTable[counter].PirqEntry[i].IrqMask;
                break;
            }
        }
        if (counter == gAddonPciBusIndx) return EFI_NOT_FOUND;   // Device not found
    }

    //
    // Find the index of given register within RRegs
    //
    rrIndx = SBGen_GetPIRQIndex (rr);
    if (rrIndx == 0xFF) return EFI_UNSUPPORTED;

    //
    // Get the programmed interrupt number off the LegacyInterrupt for rrIndx
    //
    Status = pBS->LocateProtocol (&gEfiLegacyInterruptProtocolGuid, NULL, &iInterrupt);
    if (EFI_ERROR(Status)) return Status;

    Status = iInterrupt->ReadPirq(iInterrupt, rrIndx, &Irq);
    if (EFI_ERROR(Status)) return Status;

    *Pirq = rrIndx;
    *PciIrq = Irq;

    return EFI_SUCCESS;
}


/**
    This function inserts a new entry into AddonPciBusTable.

    @param         Bus      - new bus number
    @param         PirqData - pointer to the new PIRQ data array

    @retval        EFI_SUCCESS     Entry has been created successfully.

**/

EFI_STATUS
CreateAddonBusEntry(
    UINT8   Bus,
    VOID    *PirqData
)
{
    UINT8   i;

    // Check if the requested entry already exists in AddonPciBusTable. This will be
    // the case when PCI bus driver has been reconnected.
    //
    for (i = 0; i < gAddonPciBusIndx; i++) {
        if (AddonPciBusTable[i].Bus == Bus) {
            return EFI_SUCCESS;
        }
    }

    //
    // Insert new entry into AddonPciBusTable
    //
    AddonPciBusTable[gAddonPciBusIndx].Bus = Bus;
    pBS->CopyMem(
            AddonPciBusTable[gAddonPciBusIndx].PirqEntry,
            PirqData,
            sizeof(EFI_LEGACY_PIRQ_ENTRY)*4
    );
    TRACE((-1, "AddonPciBusTable entry [%d] created: Bus %x, PIRQs: %x %x %x %x\n",
        gAddonPciBusIndx,
        AddonPciBusTable[gAddonPciBusIndx].Bus,
        AddonPciBusTable[gAddonPciBusIndx].PirqEntry[0].Pirq,
        AddonPciBusTable[gAddonPciBusIndx].PirqEntry[1].Pirq,
        AddonPciBusTable[gAddonPciBusIndx].PirqEntry[2].Pirq,
        AddonPciBusTable[gAddonPciBusIndx].PirqEntry[3].Pirq));

    gAddonPciBusIndx++;
    ASSERT(gAddonPciBusIndx<MAX_ADDITIONAL_P2P_BRIDGES);
    return EFI_SUCCESS;
}


/**
    This function verifies whether device is P2P bridge which is
    not listed in BusNumXlat table, e.g. bridge is on PCI Add-on card.
    For this bridge we create a new entry in AddonPciBusTable which
    will be used for PCI IRQ routing for the devices that are located
    behind this bridge.

    @param         PciIo       Pointer to EFI_PCI_IO_PROTOCOL protocol associated with this device
    @param         BusDev      PCI bus location for this device

    @retval        EFI_SUCCESS     Device is P2P bridge; if bus generated by this bridge is not
        described in $PIR table, then another entry in AddonPciBusTable
        is created.
    @retval        EFI_NOT_FOUND   Device is not P2P bridge.

**/

EFI_STATUS
CheckP2PBridge(
    EFI_PCI_IO_PROTOCOL *PciIo,
    UINT8               Bus,
    UINT8               Dev
)
{
    EFI_STATUS  Status;
    UINT16      PciClassSubclass;
    UINT8       SecBusNum;
    EFI_LEGACY_IRQ_ROUTING_ENTRY    *re;
    UINT8       counter;

    //
    // Check if this is a P2P bridge
    //
    Status = PciIo->Pci.Read(PciIo, EfiPciIoWidthUint16, 0xA, 1, &PciClassSubclass);
    ASSERT_EFI_ERROR(Status);
    //
    // Base Class=6 Ofs B (Bridge), SubClass=4 Ofs A (P2P Bridge)
    //
    if (PciClassSubclass != 0x0604) return EFI_NOT_FOUND;
    //
    // It is P2P bridge - read its secondary bus number and try to find a match in
    // BusNumXlat table
    //
    TRACE((-1,"P2P bridge.\n"));
    Status = PciIo->Pci.Read(
                PciIo,
                EfiPciIoWidthUint8,
                PCI_BRIDGE_SECONDARY_BUS_REGISTER_OFFSET,
                1,
                &SecBusNum);
    ASSERT_EFI_ERROR(Status);

    //
    // Locate the P2P bridge as PCI device in $PIR and get EFI_LEGACY_IRQ_ROUTING_ENTRY;
    // if found - insert new entry into AddonPciBusTable and return EFI_SUCCESS,
    // otherwise return EFI_NOT_FOUND.
    //
    for (re = gPrt, counter = 0; counter < gUsedPciEntries; counter++, re++) {
        if ((re->Bus == Bus) && (re->Device == (Dev << 3))) {
            return CreateAddonBusEntry(SecBusNum, re->PirqEntry);
        }
    }
    //
    // Not found in $PIR table - try AddonPciBusTable
    //

    if (gAddonPciBusIndx == 0) return EFI_NOT_FOUND; // No additional P2P bridges

    for (counter = 0; counter < gAddonPciBusIndx; counter++) {
        if (AddonPciBusTable[counter].Bus == Bus) {
            //
            // Connected to an add-on bridge, do the INT pin swizzling
            //
            EFI_LEGACY_PIRQ_ENTRY PirqData[4];
            UINT8 RoundRobinXlatTable[4][4] = {
                0, 1, 2, 3,
                1, 2, 3, 0,
                2, 3, 0, 1,
                3, 0, 1, 2
            };
            UINT8 i = (UINT8)(Dev % 4);
            UINT8 counter1;

            for (counter1 = 0; counter1 < 4; counter1++) {
                pBS->CopyMem(
                    &PirqData[counter1],
                    &AddonPciBusTable[counter].PirqEntry[RoundRobinXlatTable[i][counter1]],
                    sizeof(EFI_LEGACY_PIRQ_ENTRY)
                );
            }

            return CreateAddonBusEntry(SecBusNum, PirqData);
        }
    }
    return EFI_NOT_FOUND;
}


/**
    Returns the ISA interrupt mask

    @retval UINT16 ISA interrupt mask

**/

EFI_STATUS
GetIsaIrqs(
    OUT UINT16  *IrqMask
)
{
    EFI_STATUS Status;

    Status=AmiIsaIrqMask(IrqMask, TRUE);
    if(EFI_ERROR(Status)){
        *IrqMask = ISA_IRQ_MASK;    // allow IRQ 0..8, 12..15 for ISA
        Status=AmiIsaIrqMask(IrqMask, FALSE);
		TRACE(((UINTN)-1, "PciInterrupts: Set ISA_IRQ_MASK, Status=%r\n", Status));
    }
    return Status;
}


/**
    This function assigns IRQ to a PCI device. It programs PCI
    IRQ register if:
    - device requires IRQ to be assigned (reg 3D is 1..3)
    - PCI bus information is updated in $PIR table (step 1
    has been completed)
    - device is present in IRQ routing table
    When all these conditions are met, then IRQ is assigned to this
    device according to the PCI IRQ priorities; then IRQ is programmed
    in PCI register 3C

    @param PCI Bus, Device and Function number of the PCI device

**/

VOID ProgramPciIrq(
    EFI_PCI_IO_PROTOCOL *PciIo,
    VOID *Context)
{
    UINT16      IrqMask;
    UINT16      Mask, EdgeLevel;
    UINT8       Int, Irq;
    EFI_STATUS  Status;
    EFI_LEGACY_BIOS_PLATFORM_PROTOCOL *iBiosPlatform;
    UINTN Seg, Bus, Dev, Func;
    UINTN       OemIrqMask;

    Status = PciIo->GetLocation(PciIo, &Seg, &Bus, &Dev, &Func);
    ASSERT_EFI_ERROR(Status);
    if (EFI_ERROR(Status)) return;

    TRACE((-1,"PCI device: %x %x %x ... ", Bus, Dev, Func));

    //
    // Find out whether device is P2P bridge that is not listed in BusXlatNum table;
    // if found - add entry into P2P bridge table and exit. Function returns:
    // EFI_NOT_FOUND - not P2P bridge; EFI_SUCCESS - P2P bridge found and bridge table
    // is updated.
    //
    Status = CheckP2PBridge(PciIo, (UINT8)Bus, (UINT8)Dev);

    //
    // Check if device requires IRQ
    //
    Status = PciIo->Pci.Read(PciIo, EfiPciIoWidthUint8, 0x3D, 1, &Int);
    ASSERT_EFI_ERROR(Status);

    if (EFI_ERROR(Status)) return;

    if (!Int) {
        TRACE((-1,"does not require IRQ.\n"));
        return; // Device does not support IRQ
    }

    Int--;              // Zero based INTx
    iBiosPlatform = (EFI_LEGACY_BIOS_PLATFORM_PROTOCOL *)Context;
    Status = Intx2Pirq(
                    iBiosPlatform,
                    Bus, Dev, Func,
                    &Int,
                    &Irq,
                    &IrqMask);

    TRACE((-1,"Intx2Pirq returns %r\n", Status));

    if (Status == EFI_NOT_FOUND) {
        // Device is not in the table
        TRACE((-1,"Device is not found in the PCI IRQ routing table.\n"));
    }

    if (EFI_ERROR(Status)) {
        return;
    }

    //
    // Intx2Pirq returns:
    //  Int - routing register index;
    //  Irq - interrupt currently programmed in that index.
    //  IrqMask - bit mask of the interrupts that can possibly be
    //  assigned to this device.
    //

    // Call OEM function that can modify the list of interrupts that can be
    // assigned. Note that the list can only be shrunk, not extended.
/* replace a non-standard EfiGetPlatformPciIrqMask with the direct custom call
    Status = gCoreBiosInfo->iBiosPlatform->GetPlatformInfo(
                    gCoreBiosInfo->iBiosPlatform,
                    EfiGetPlatformPciIrqMask,
                    &PciIo,
                    NULL, NULL,
                    &OemIrqMask,
                    IrqMask,
                    0);*/
	Status = GetCustomPciIrqMask (PciIo, IrqMask, &OemIrqMask);

    if (!EFI_ERROR(Status)) {
        IrqMask &= (UINT16)OemIrqMask;
    }

    Status = RoutePciIrq(Int, &Irq, IrqMask);

    TRACE((-1,"RoutePciIrq status: %r,  Intx2Pirq: Int %x IRQ %x mask %x\n", Status, Int, Irq, IrqMask));

    Status = PciIo->Pci.Write(PciIo, EfiPciIoWidthUint8, 0x3C, 1, &Irq);
    ASSERT_EFI_ERROR(Status);

    if (EFI_ERROR(Status)) return;

    //
    // Set corresponding mask and edge/level mode in 8259 for real mode operation
    //
    i8259->GetMask(i8259, &Mask, &EdgeLevel, NULL, NULL);
    Mask &= (UINT16)~(1 << Irq);
    EdgeLevel |= (UINT16)(1 << Irq);
    i8259->SetMask(i8259, &Mask, &EdgeLevel, NULL, NULL);

    // Send out a word about programmed PCI interrupt
    gIrqPgmCtx.PciIo = (VOID*)PciIo;
    gIrqPgmCtx.Irq = Irq;
    Status = pBS->ReinstallProtocolInterface(
        gHandle,
        &gAmiPciIrqProgramGuid,
        &gIrqPgmCtx,
        &gIrqPgmCtx
    );
    ASSERT_EFI_ERROR(Status);
}


/**
    This function updates $PIR table with the actual PCI bus numbers.

**/

VOID UpdatePrt()
{
    EFI_STATUS Status;
    UINTN count1;//, count2, count3;
    UINT8 *p;
    UINT8 chksum = 0;
    UINTN PrtAddress;
	UINT32 Granularity;
//-----------------------------------------
    //Check if PciBus Driver updated IRQ Routing Table yet
    ASSERT(gAmiBoardInfo2Protocol->DataValid==TRUE);
    
    // Create an instance of IRQ ROUTING table with $PIR Header
    p = MallocZ(sizeof(EFI_LEGACY_PIRQ_TABLE_HEADER) + gAmiBoardInfo2Protocol->PicRoutLength);
    MemCpy(p, gMsPrt, sizeof(EFI_LEGACY_PIRQ_TABLE_HEADER));

    // Since $PRT Table header was created separately, free memory used for Header
	// instance since we have copied it already.
    pBS->FreePool(gMsPrt);
    gMsPrt = (EFI_LEGACY_PIRQ_TABLE_HEADER*)p;
    p += sizeof(EFI_LEGACY_PIRQ_TABLE_HEADER);
    MemCpy(p, gAmiBoardInfo2Protocol->PicRoutTable, gAmiBoardInfo2Protocol->PicRoutLength);

    //Update gUsedPciEntries since PciBus Driver removed unused entries from the table
    gUsedPciEntries = gAmiBoardInfo2Protocol->PicRoutLength/sizeof(PCI_IRQ_PIC_ROUTE);
    
    //Update Prt pointer to point at new instance of table and header together.
    gPrt = (EFI_LEGACY_IRQ_ROUTING_ENTRY*)p;
    
    //Update table size in $PRT header 
    gMsPrt->TableSize = (UINT16)(gAmiBoardInfo2Protocol->PicRoutLength + sizeof(EFI_LEGACY_PIRQ_TABLE_HEADER));

    // Call BSP routine to do Chipset/OEM specific modifications to PRT
    BspUpdatePrt(&gCoreBiosInfo->iBios, gMsPrt);

    // Checksum the table
    for (count1 = 0; count1 < gMsPrt->TableSize; count1++) {
        chksum = chksum + *((UINT8*)gMsPrt+count1);
    }
    gMsPrt->Checksum = (~chksum) + 1;
    gIsValidPrt = TRUE;

    // Copy PCI routing table to F000
    PrtAddress = CopyLegacyTable(
                gMsPrt,
                (UINT16)gMsPrt->TableSize,
                1,  // alignment
                F0000_BIT);
    ASSERT(PrtAddress);
	TRACE((-1, "$PIR table, %d Bytes long, is copied to %x\n", (UINT16)gMsPrt->TableSize, PrtAddress));

    Status = gCoreBiosInfo->iRegion->UnLock (gCoreBiosInfo->iRegion, 0xF0000, 0x10000, &Granularity);
    ASSERT_EFI_ERROR(Status);

    gCoreBiosInfo->Csm16Header->IrqRoutingTablePointer = (UINT32)PrtAddress;
    gCoreBiosInfo->Csm16Header->IrqRoutingTableLength = (UINT32)gMsPrt->TableSize;

    Status = gCoreBiosInfo->iRegion->Lock (gCoreBiosInfo->iRegion, 0xF0000, 0x10000, &Granularity);
    ASSERT_EFI_ERROR(Status);
/*
    BusNumXlatProtocol.GetXlatPciBusNum = GetXlatPciBusNumber;
    Status = pBS->InstallProtocolInterface(
        &gHandle,
        &gBusNumXlatProtocol,
        EFI_NATIVE_INTERFACE,
        &BusNumXlatProtocol
    );
    ASSERT_EFI_ERROR(Status);
	*/
}


/**
    PciIo notification callback. It serves two purposes:
    1) Updates bus numbers in BusNumXlat table and in $PIR table. For
    this the routine uses PciIo to get PCI loaction of the handle;
    then it will match the found dev/fun against BusNumXlat table
    and fill global RootBridges data and update bus numbers in
    $PIR table.
    2) Programs PCI IRQ register if:
    - device requires IRQ to be assigned (reg 3D is 1..3)
    - PCI bus information is updated in $PIR table (step 1
    has been completed)
    - device is present in IRQ routing table
    When all these conditions are met, then IRQ is assigned to this
    device according to the PCI IRQ priorities; then IRQ is programmed
    in PCI register 3C

    @param Event - event signaled by the DXE Core upon PciIo installation
    @param Context - event context


**/

VOID PciIoNotifyCallback (
    EFI_EVENT Event,
    VOID      *Context)
{
//  UINT16      counter;
    UINTN       BufferSize = sizeof(EFI_HANDLE);
    EFI_HANDLE  Handle;
    EFI_PCI_IO_PROTOCOL *PciIo;
    EFI_STATUS  Status;
    BOOLEAN     IsRootBridge;
    UINT8       dData[4];
    UINT16      IrqMask;
    EFI_LEGACY_INTERRUPT_PROTOCOL       *iInterrupt;
    UINT8       Dev, Func;
//------------------------------------------
    if (!gIsValidPrt) {
		Status = pBS->LocateProtocol (&gEfiLegacyInterruptProtocolGuid, NULL, &iInterrupt);
		TRACE((TRACE_ALWAYS,"CSM PciIrq: Locating LegacyInterrupts Protocol...Status=%r\n",Status));
		if (EFI_ERROR(Status)) return;

		//
		// Initialize global variables
		//
		gMsPrt = MallocZ(sizeof(EFI_LEGACY_PIRQ_TABLE_HEADER));
		ASSERT(gMsPrt!=NULL);

		gPrt = (EFI_LEGACY_IRQ_ROUTING_ENTRY*)gAmiBoardInfo2Protocol->PicRoutTable;
		gUsedPciEntries=gAmiBoardInfo2Protocol->PicRoutLength/sizeof(PCI_IRQ_PIC_ROUTE);
		gIsValidPrt = FALSE; // becomes TRUE after bus numbers and checksum in $PRT table is updated

		pBS->SetMem(AddonPciBusTable, sizeof(EFI_ADDON_PCIBUS_TABLE)*MAX_ADDITIONAL_P2P_BRIDGES, 0);
		gAddonPciBusIndx = 0;

		//Fill gMsPrt Structure Header
		gMsPrt->Signature=0x52495024; //"$PIR"
		gMsPrt->MinorVersion=0; //version (low byte = minor, high byte = major)
		gMsPrt->MajorVersion=1;
		gMsPrt->TableSize=sizeof(EFI_LEGACY_PIRQ_TABLE_HEADER); //It's only header for now we will connet header and table later

		Status = iInterrupt->GetLocation(iInterrupt, &gMsPrt->Bus, &Dev, &Func);
		if (EFI_ERROR(Status)) return;

		gMsPrt->DevFun = (Dev << 3) + Func;
		gMsPrt->CompatibleVid = SB_PIRQ_ROUTER_VID;
		gMsPrt->CompatibleDid = SB_PIRQ_ROUTER_DID;

		MemSet(irq_allocated_count, sizeof(irq_allocated_count), 0);

		//
		// Set 8259 interrupt mask for 16 bit mode
		//
		Status = pBS->LocateProtocol (&gEfiLegacy8259ProtocolGuid, NULL, &i8259);
		ASSERT_EFI_ERROR(Status);

        //
        // Set 8259 interrupt mask for 16 bit mode
        //
        Status = GetIsaIrqs(&IsaIrqMask);   // Ones for ISA IRQs
        TRACE((-1, "PciInterrupts: Init PRT Get ISA_IRQ_MASK, Status=%r\n", Status));
        ASSERT_EFI_ERROR(Status);

        IrqMask = ~IsaIrqMask;

        Status = i8259->SetMask(i8259, &IrqMask, NULL, NULL, NULL);
        ASSERT_EFI_ERROR(Status);

        UpdatePrt();    // Update bus numbers in PRT
    }

    //gBS->LocateProtocol(&gEfiPciIoProtocol, gPciIoNotifyReg, &PciIo);
    Status = pBS->LocateHandle(ByRegisterNotify,
                NULL, gPciIoNotifyReg, &BufferSize, &Handle);
    ASSERT_EFI_ERROR(Status);
    if (EFI_ERROR(Status)) return;

    //
    // Locate PciIo protocol installed on Handle
    //
    Status = pBS->HandleProtocol(Handle, &gEfiPciIoProtocolGuid, &PciIo);
    ASSERT_EFI_ERROR(Status);
    if (EFI_ERROR(Status)) return;

    //
    // Check whether Handle is PCI Root Bridge handle.
    //
    Status = PciIo->Pci.Read(
        PciIo,
        EfiPciIoWidthUint32,
        8,  // offset
        1,  // width
        &dData);
    ASSERT_EFI_ERROR(Status);
    if (EFI_ERROR(Status)) return;

    IsRootBridge = (dData[1] == 0) &&
        (dData[2] == PCI_CL_BRIDGE_SCL_HOST) &&
        (dData[3] == PCI_CL_BRIDGE);    // Host bridge

    if (IsRootBridge) return;   // Do not process root bridges

    ProgramPciIrq(PciIo, Context);
}


/**
    Initialize PCI IRQ routing table

    @param iBiosPlatform - pointer to EFI_LEGACY_BIOS_PLATFORM_PROTOCOL

    @retval EFI_STATUS - PCI IRQ routing table initialization status

    @note  1) BusNumXlat table is generated by AMISDL and requires the actual PCI bus
          number fields to be updated. Actual PCI bus numbers can not be obtained at
          this time due to the following:
          - BusNumXlat table provides PCI dev/fun information for the PCI Host Bridge(s),
            whereas PciRootBridgeIo does not give PCI dev/fun information; in case of
            several PCI Root Bridges, PCI location ambiguity can not be resolved at this
            time.
          - PCI Dev/Fun information for PCI Host bridge(s) is not available until
            PCI Bus driver installs PciIo on PCI Host Bridge devices' handles.

          2) In order to match PCI Root Bridge(s) against the BusNumXlat table, we
          will register callback notification function that will be called every
          time PciIo protocol is installed. This would identify every PCI RootBridge's
          PCI location(dev/fun), allow to match this PCI location against XlatBusNum table,
          traverse through XlatBusNum table entry and update the appropriate bus number
          fields in $PIR table.

          3) This driver must be made dependent on PciRootBridge driver since it expects
          the valid information about PCI root bridges.
**/

EFI_STATUS InitPrt(
    EFI_LEGACY_BIOS_PLATFORM_PROTOCOL   *iBiosPlatform
)
{
    EFI_EVENT   Event;
    EFI_STATUS  Status;

    //
    // Create the notification and register callback function on the PciIo installation,
    // callback function will update $PIR table
    //
    Status = pBS->CreateEvent (
        EVT_NOTIFY_SIGNAL,
        TPL_CALLBACK,
        PciIoNotifyCallback,
        iBiosPlatform,
        &Event);
    ASSERT_EFI_ERROR(Status);
    if (EFI_ERROR(Status)) return Status;

    Status = pBS->RegisterProtocolNotify (
        &gEfiPciIoProtocolGuid,
        Event,
        &gPciIoNotifyReg);
    ASSERT_EFI_ERROR(Status);

    // Install PCI IRQ programming interface
    gIrqPgmCtx.PciIo = NULL;
    gIrqPgmCtx.Irq = 0;
    Status = pBS->InstallProtocolInterface(
        &gHandle,
        &gAmiPciIrqProgramGuid,
        EFI_NATIVE_INTERFACE,
        &gIrqPgmCtx
    );
    ASSERT_EFI_ERROR(Status);

    return Status;
}


//**********************************************************************
//**********************************************************************
//**                                                                  **
//**        (C)Copyright 1985-2014, American Megatrends, Inc.         **
//**                                                                  **
//**                       All Rights Reserved.                       **
//**                                                                  **
//**           5555 Oakbrook Pkwy, Suite 200, Norcross, GA 30093      **
//**                                                                  **
//**                       Phone: (770)-246-8600                      **
//**                                                                  **
//**********************************************************************
//**********************************************************************
